package com.learn.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author bill
 * @version 1.0
 * @date 2021/8/13 16:33
 * @description
 */
@Slf4j
public class RedisLock {

    private static final String LIST_PREFIX= "LIST_REDIS_FAIR_KEYS:";
    private static final String HASN_PREFIX= "HASH_REDIS_FAIR_KEYS:";
    private StringRedisTemplate stringRedisTemplate;
    private volatile boolean isLocked = false;
    private String key;
    private long timeOut;
    private Integer sord;
    public RedisLock(StringRedisTemplate stringRedisTemplate,String key,long timeout){
        this.stringRedisTemplate=stringRedisTemplate;
        this.key=key;
        this.timeOut=timeout;
    }
    public RedisLock(StringRedisTemplate stringRedisTemplate,String key,long timeout,Integer sord){
        this.stringRedisTemplate=stringRedisTemplate;
        this.key=key;
        this.timeOut=timeout;
        this.sord=sord;
    }
    public boolean isLock() {
        return this.stringRedisTemplate.hasKey(this.key);
    }

    /**
     * 排他锁
     * @return
     */
    public boolean tryMutexLock() {
        long nowTime = System.currentTimeMillis();
        String expireStr = String.valueOf(nowTime + this.timeOut);
        try {
            if (this.stringRedisTemplate.opsForValue().setIfAbsent(this.key, expireStr)) {
                this.stringRedisTemplate.expire(this.key, this.timeOut, TimeUnit.MILLISECONDS);
                this.isLocked = true;
                log.debug("加锁成功[key,value]=[" + this.key + "," + expireStr + "]");
                return true;
            }
            this.protection();
        } catch (Exception var5) {
            this.protection();
            log.error("redis加锁异常,[key=" + this.key + "],[value=" + expireStr + "]" + var5);
        }

        return false;
    }
    public void unlock(){
        if (this.isLocked) {
            this.stringRedisTemplate.delete(this.key);
            log.debug("解锁锁成功[key]=[" + this.key + "]");
        }
    }

    /**
     * 非公平锁
     * @return
     */
    public boolean tryLockUnFair() {
        long nowTime = System.currentTimeMillis();
        String expireStr = String.valueOf(nowTime + this.timeOut);
        Random r = new Random();

        try {
            while(System.currentTimeMillis() - nowTime < this.timeOut) {
                if (tryLock(expireStr)){
                    return true;
                }
                this.protection();
                Thread.sleep(100L, r.nextInt(500));
            }
        } catch (Exception ex) {
            this.protection();
            log.error("redis加锁异常,[key=" + this.key + "],[value=" + expireStr + "]" + ex);
        }
        return false;
    }

    /**
     * 公平锁
     * @return
     */
    public boolean tryLockFair() {
        long nowTime = System.currentTimeMillis();
        String expireStr = String.valueOf(nowTime + this.timeOut);
        Random r = new Random();
        String listKey=LIST_PREFIX + key;
        String hashKey=HASN_PREFIX + key;
        //线程请求标记
        String uuid = UUID.randomUUID().toString();
        //查看当前队列是否有在等待
        long size = stringRedisTemplate.boundListOps(listKey).size().longValue();
        if (  size==0  && tryLock(expireStr) ){
            log.info("redis加锁成功,[key=" + this.key );
            return true;
        }
        try {
            //存储过期时间
            stringRedisTemplate.boundHashOps(hashKey).put(uuid,expireStr);
            //存储请求顺序
            stringRedisTemplate.boundListOps(listKey).rightPush(uuid);
            // 如果等待时间未过期
            while(System.currentTimeMillis() - nowTime < this.timeOut) {
                // 取出 头元素
                String value = stringRedisTemplate.boundListOps(listKey).index(0);
                // 如果头元素是自己 尝试获取锁
                if (uuid.equals(value) && tryLock(expireStr)){
                    // 获取成功 则从队列中移除自己
                    stringRedisTemplate.boundHashOps(hashKey).delete(uuid);
                    stringRedisTemplate.boundListOps(listKey).leftPop();
                    log.info("redis加锁成功,[key=" + this.key );
                    return true;
                }
                // 如果获取锁失败，判断头元素是否等待过期
                Object object = stringRedisTemplate.boundHashOps(hashKey).get(value);
                if (null!=object && Long.parseLong((String) object)<System.currentTimeMillis()){
                    // 如果过期，将其移除队列
                    stringRedisTemplate.boundListOps(listKey).remove(1,value);
                    stringRedisTemplate.boundHashOps(hashKey).delete(value);
                }
                this.protection();
                Thread.sleep(50L, r.nextInt(500));
            }
        } catch (Exception ex) {
            this.protection();
            log.error("redis加锁异常,[key=" + this.key + "],[value=" + expireStr + "]" + ex);
        }
        log.error("redis加锁失败,[key=" + this.key );
        //如果未能获取成功，将自己的信息删掉
        stringRedisTemplate.boundListOps(listKey).remove(0,uuid);
        stringRedisTemplate.opsForHash().delete(hashKey,uuid);
        return false;
    }

    private boolean tryLock(String expireStr) {
        if (this.stringRedisTemplate.opsForValue().setIfAbsent(this.key, expireStr)) {
            this.stringRedisTemplate.expire(this.key, 30L, TimeUnit.SECONDS);
            this.isLocked = true;
            log.info("加锁成功[key,value]=[" + this.key + "," + expireStr + "]");
            return true;
        }
        return false;
    }

    private void protection() {
        if (this.stringRedisTemplate.getExpire(this.key) == -1L) {
            String currentValueStr = (String)this.stringRedisTemplate.opsForValue().get(this.key);
            if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
                this.stringRedisTemplate.delete(this.key);
            } else {
                this.stringRedisTemplate.expire(this.key, this.timeOut, TimeUnit.MILLISECONDS);
            }
        }
    }
}
